Skip to content

feat(cloudflare): Capture request body via httpServerIntegration#20614

Open
JPeer264 wants to merge 1 commit intodevelopfrom
jp/capture-request-body-cloudflare-workers
Open

feat(cloudflare): Capture request body via httpServerIntegration#20614
JPeer264 wants to merge 1 commit intodevelopfrom
jp/capture-request-body-cloudflare-workers

Conversation

@JPeer264
Copy link
Copy Markdown
Member

@JPeer264 JPeer264 commented Apr 30, 2026

closes #17079
closes JS-751

This PR is basically adding and exporting captureBodyFromWinterCGRequest from @sentry/core and re-implementing httpServerIntegration for Cloudflare. There was no way to reuse the existing httpServerIntegration, as it is using patchRequestToCaptureBody, which wouldn't work in Cloudflare, as there is too late to be patched, so captureBodyFromWinterCGRequest was born.

The original httpServerIntegration is also taking care of other SDK processing metadata via httpRequestToRequestData, which happens already for every request in Cloudflare via the addHandler in the scope-utils.ts. I still tried to reuse as much code as possible (that's the reason for the new exposed functionality in @sentry/core).

Two options from the original integration are missing sessions and sessionFlushingDelayMS. I don't think this can be implemented 1:1, that is why I left it out, as the sessions seem to be aggregated, which wouldn't work in Cloudflare, as we generate a new client for each request. And I think it is better if we don't enable that by default, as otherwise every request would send a session by default.

In theory this would also be usable for other runtimes like Bun or Deno if I'm not mistaken

@JPeer264 JPeer264 self-assigned this Apr 30, 2026
@linear-code
Copy link
Copy Markdown

linear-code Bot commented Apr 30, 2026

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 30, 2026

size-limit report 📦

Path Size % Change Change
@sentry/browser 26.16 kB - -
@sentry/browser - with treeshaking flags 24.63 kB - -
@sentry/browser (incl. Tracing) 44.13 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 46.34 kB - -
@sentry/browser (incl. Tracing, Profiling) 49.08 kB - -
@sentry/browser (incl. Tracing, Replay) 83.48 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 72.96 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 88.15 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 100.8 kB - -
@sentry/browser (incl. Feedback) 43.4 kB - -
@sentry/browser (incl. sendFeedback) 30.96 kB - -
@sentry/browser (incl. FeedbackAsync) 36.14 kB - -
@sentry/browser (incl. Metrics) 27.44 kB - -
@sentry/browser (incl. Logs) 27.59 kB - -
@sentry/browser (incl. Metrics & Logs) 28.28 kB - -
@sentry/react 27.9 kB - -
@sentry/react (incl. Tracing) 46.36 kB - -
@sentry/vue 31.03 kB - -
@sentry/vue (incl. Tracing) 45.96 kB - -
@sentry/svelte 26.18 kB - -
CDN Bundle 28.85 kB - -
CDN Bundle (incl. Tracing) 46.91 kB - -
CDN Bundle (incl. Logs, Metrics) 30.27 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 48.03 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 69.35 kB - -
CDN Bundle (incl. Tracing, Replay) 84.07 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 85.14 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 89.86 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 90.96 kB - -
CDN Bundle - uncompressed 84.55 kB - -
CDN Bundle (incl. Tracing) - uncompressed 140.16 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 88.75 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 143.62 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 212.71 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 257.96 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 261.41 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 271.66 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 275.1 kB - -
@sentry/nextjs (client) 48.85 kB - -
@sentry/sveltekit (client) 44.58 kB - -
@sentry/node-core 59.06 kB +0.03% +16 B 🔺
@sentry/node 170.37 kB +0.02% +28 B 🔺
@sentry/node - without tracing 96.93 kB +0.02% +17 B 🔺
@sentry/aws-serverless 113.78 kB +0.04% +35 B 🔺
@sentry/cloudflare (withSentry) - minified 166.52 kB +0.95% +1.56 kB 🔺
@sentry/cloudflare (withSentry) 420.85 kB +0.9% +3.75 kB 🔺

View base workflow run

@JPeer264 JPeer264 force-pushed the jp/capture-request-body-cloudflare-workers branch 2 times, most recently from ff11476 to e0f8ddf Compare May 4, 2026 08:28
@JPeer264 JPeer264 force-pushed the jp/capture-request-body-cloudflare-workers branch from e0f8ddf to 0562008 Compare May 4, 2026 08:32
@JPeer264 JPeer264 requested review from chargome, isaacs and nicohrubec May 4, 2026 08:33
@JPeer264 JPeer264 marked this pull request as ready for review May 4, 2026 08:33
@JPeer264 JPeer264 requested a review from a team as a code owner May 4, 2026 08:33
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0562008. Configure here.

const bodyPromise = clonedRequest.text();
const timeoutPromise = new Promise<null>(resolve => {
setTimeout(() => resolve(null), 2000);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing safeUnref on setTimeout in core package

Medium Severity

The setTimeout in captureBodyFromWinterCGRequest doesn't call safeUnref(), which could keep Node.js/Bun/Deno processes alive for up to 2 seconds after the main work completes. This is in @sentry/core, a server runtime package. The safeUnref utility exists in the same package at utils/timer.ts and is used elsewhere (e.g., promisebuffer.ts wraps setTimeout with safeUnref in the exact same Promise.race timeout pattern). The PR description notes this code is intended to be reusable for Bun and Deno, which support unref.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit 0562008. Configure here.

if (maxRequestBodySize === 'small') return 1_000;
if (maxRequestBodySize === 'medium') return 10_000;
return MAX_BODY_BYTE_LENGTH;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getMaxBodyByteLength returns 1MB for 'none' input

Medium Severity

getMaxBodyByteLength accepts the full MaxRequestBodySize type (including 'none') but doesn't handle 'none' explicitly — it falls through to return MAX_BODY_BYTE_LENGTH (1MB). This is the same value returned for 'always', making 'none' behave identically to "capture everything." The function is publicly exported from @sentry/core and the patchRequestToCaptureBody parameter type was also widened to accept MaxRequestBodySize, increasing the risk of a caller passing 'none' without an external guard.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0562008. Configure here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid point I think

Comment on lines +88 to +90
if (client) {
await captureIncomingRequestBody(client, request);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: Can be moved below the OPTIONS and HEAD check

}

// Skip GET and HEAD requests - they don't have bodies
if (request.method === 'GET' || request.method === 'HEAD') {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: OPTIONS is fine?

if (maxRequestBodySize === 'small') return 1_000;
if (maxRequestBodySize === 'medium') return 10_000;
return MAX_BODY_BYTE_LENGTH;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid point I think

Comment on lines +34 to +38
export function getMaxBodyByteLength(maxRequestBodySize: MaxRequestBodySize): number {
if (maxRequestBodySize === 'small') return 1_000;
if (maxRequestBodySize === 'medium') return 10_000;
return MAX_BODY_BYTE_LENGTH;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should likely handle 'none' in here too?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Capture request body in cloudflare workers

2 participants